feat: self-contained MCP host extensions for stateless engine construction#226
Merged
Merged
Conversation
…ction Prepared MCP host extensions now embed their resolved server config and cached catalog, so the toolkit resolves and executes tools from that embedded data instead of re-reading the MCP config/catalog from context.stateRoot at runtime. Why: an embedder that builds a fresh conversation engine per request (e.g. a multi-tenant server with a per-user API key and injected per-user storage) had to prep MCP under every engine's stateRoot, forcing a per-request config write + catalog refresh + a per-user .heddle/ directory. Now one prepared extension is reused across cheap per-request engines with zero per-engine MCP prep. - prepareMcpHostExtension returns a self-contained extension (embeds resolvedServer + catalog); execution goes straight to McpClientService using the embedded config (already stateless: spawn-per-call, then close), honoring the same allow/deny guard McpService applies. - Backward compatible: defineMcpHostExtension(options) with no embedded data keeps the stateRoot-reading path (the local CLI host is unchanged). - Adds a unit test proving resolution + execution work against an EMPTY stateRoot and never touch McpService. Refs: heddle-workspace-notes stateless-engine design (Phase A step 1). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CjFdZgZhov8wdwFinVTfjz
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
An embedder that builds a fresh conversation engine per request (e.g. a multi-tenant server: per-user API key + injected per-user storage) currently has to prep MCP under every engine's
stateRoot— a per-request config write + catalog refresh + a per-user.heddle/directory. The MCP host extension re-read the MCP config/catalog fromcontext.stateRootat runtime, so it was not reusable across cheap per-request engines.This is the Phase A enhancement identified while pressure-testing Heddle against a real server integration: make engine construction effectively stateless so one prepared MCP extension can be shared across many per-request engines.
What
prepareMcpHostExtension(...)now returns a self-contained extension: it embeds the resolved server config + cached catalog. At runtime the toolkit resolves tools from that embedded data and executes them straight throughMcpClientServiceusing the embedded config — nostateRootread.McpClientService, then close); the embedded config just removes thestateRootdependency. The same allow/deny guardMcpService.callToolapplies is preserved.defineMcpHostExtension(options)with no embedded data keeps the originalstateRoot-reading path (the local CLI host is unchanged). Preparation passes embedded data via the optional 2nd argdefineMcpHostExtension(options, resolved).Result
Prepare the MCP extension once (one spawn + catalog refresh), then reuse the same extension across per-request engines with per-user key + injected stores — zero per-engine MCP prep, no per-user
.heddle/mcp*.Tests
stateRoot, honoring deny policy, and never calls thestateRoot-backedMcpService.buildclean.🤖 Generated with Claude Code